#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Projet CellMates
# Judith ROBSON
# Roxane LEVY
# Ludovic JURADO

import copy
import Globals
import transRegles
from PySide.QtCore import *
from PySide.QtGui import *

"""	Classe "IMAGEBUTTON".
	Elle représente une image cliquable.

Elle reconnaît le clique gauche et le double clique.
Il est possible de choisir une couleur, une taille, ainsi que son activation/sa désactivation.
Une image cliquable désactivée sera affichée en gris, sinon, elle sera affichée avec la couleur sélectionnée.
Elle est utilisée pour représenter la couleur des états.

Dans cette classe, on trouve :
- une fonction de choix de couleur
- une fonction renvoyant la couleur courante
- une fonction d'activation/désactivation
- une fonction renvoyant l'état actif/inactif courant
- une fonction de choix de taille
- des fonctions gérant les signaux (cliques)"""

class ImageButton(QLabel) :

	# Déclaration des signaux
	clicked = Signal()
	doubleClicked = Signal()


	# Fonction d'initialisation
	def __init__(self, size = QSize(20, 20), parent = None) :
		QLabel.__init__(self, parent)
		self.setEnabled(True)
		self.setSize(size)


	# Fonction de choix de couleur
	def setColor(self, color) :
		self.color = color
		image = QPixmap(30, 30)

		if not self.enabled :
			image.fill(QColor("grey"))
		else :
			image.fill(self.color)

		self.setPixmap(image)


	# Fonction renvoyant la couleur
	def getColor(self) :
		return self.color


	# Fonction d'activation/désactivation
	def setEnabled(self, enabled) :
		self.enabled = enabled


	# Fonction renvoyant l'état actif/inactif
	def isEnabled(self) :
		return self.enabled


	# Fonction permettant le réglage de la taille
	def setSize(self, size) :
		self.setFixedSize(size)


	# Fonction gérant le double clique
	def mouseDoubleClickEvent(self, event) :
		if self.isEnabled() :
			self.emit(SIGNAL("doubleClicked()"))


	# Fonction gérant le clique gauche
	def mousePressEvent(self, event) :
		if event.button() == Qt.LeftButton and self.isEnabled() :
			self.emit(SIGNAL("clicked()"))




""" Classe "CASEVOISINAGE".
	Elle représente une case dans le choix du voisinage.

C'est un QGraphicsRectItem (utilisé pour générer des objets rectangulaires) auquel
du texte a été attaché afin de gérer les voisinages non totalistiques.

Dans cette classe, on trouve :
- une fonction positionnant le texte au centre de la case
- une fonction affichant un texte"""

class CaseVoisinage(QGraphicsRectItem) :

	# Fonction d'initialisation
	def __init__(self, x, y, w, h, parent = None) :
		QGraphicsRectItem.__init__(self, x, y, w, h, parent)
		self.x = x
		self.y = y
		self.w = w
		self.h = h
		self.text = QGraphicsTextItem(self)


	# Fonction permettant le positionnement du texte au centre de la case
	def setTextPos(self) :
		textw = self.text.document().size().width()
		texth = self.text.document().size().height()
		offsetx = (self.w - textw) / 2
		offsety = (self.h - texth) / 2
		self.text.setPos(self.x + offsetx, self.y + offsety)


	# Fonction affichant du texte dans la case
	def setText(self, text) :
		self.text.setPlainText(str(text))
		self.setTextPos()



""" Classe "VOISINAGE".
	Elle gère le choix du voisinage.

Elle reconnaît le clique gauche et le clique droit.
Elle permet d'afficher un voisinage de taille variant entre 1 et 3, totalistique ou non.
Un voisinage totalistique sera représenté par des cases blanches (poids nul) et noirs (poids 1).
Un voisinage non totalistique sera représenté par des cases blanches sur lesquelles le poids
est écrit.
Que le voisinage soit totalistique ou non, un clique gauche sur la case augmente son poids
de 1 et un clique droit le diminue de 1.
Pour le voisinage totalistique, le poids maximum d'une cellule est 1.
Pour le voisinage non totalistique, le poids maximum d'une cellule est égal au nombre total
de cellules dans le voisinage.

Dans cette classe, on trouve :
- une fonction permettant d'incrémenter le poids d'une cellule
- une fonction permettant de décrémenter le poids d'une cellule
- une fonction dessinant et affichant le voisinage
- une fonction mettant à jour le voisinage
- une fonction gérant les cliques gauche et droit"""

class Voisinage(QGraphicsScene) :

	# Déclaration des signaux
	clicked = Signal()
	rightClicked = Signal()


	# Fonction d'initialisation
	def __init__(self, parent = None) :
		QGraphicsScene.__init__(self, parent)
		self.widgetSize = 175
		self.setVoisinage()
		self.clicked.connect(lambda: self.incrOnClick())
		self.rightClicked.connect(lambda: self.decrOnRightClick())


	# Fonction permettant l'incrémentation du poids d'une cellule par un clique gauche
	def incrOnClick(self) :
		etat = Globals.selectedState
		x = self.positionClicked.x()
		y = self.positionClicked.y()
		i = int(y // self.cellSize)
		j = int(x // self.cellSize)

		for cellule in self.items(self.positionClicked) :
			if type(cellule) is CaseVoisinage :
				if Globals.totalistique[etat] :
					cellule.setBrush(QBrush(QColor("black")))
					Globals.poidsVoisinage[etat][i][j] = 1
				else :
					maxPoids = pow((Globals.tailleVoisinage[etat] + 1) * 2 - 1, 2)
					if Globals.poidsVoisinage[etat][i][j] < maxPoids :
						Globals.poidsVoisinage[etat][i][j] += 1
						cellule.setText(Globals.poidsVoisinage[etat][i][j])


	# Fonction permettant la décrémentation du poids d'une cellule par un clique droit
	def decrOnRightClick(self) :
		etat = Globals.selectedState
		x = self.positionClicked.x()
		y = self.positionClicked.y()
		i = int(y // self.cellSize)
		j = int(x // self.cellSize)

		for cellule in self.items(self.positionClicked) :
			if type(cellule) is CaseVoisinage :
				if Globals.totalistique[etat] :
					cellule.setBrush(QBrush(QColor("white")))
					Globals.poidsVoisinage[etat][i][j] = 0
				else :
					minPoids = 0
					if Globals.poidsVoisinage[etat][i][j] > minPoids :
						Globals.poidsVoisinage[etat][i][j] -= 1
						cellule.setText(Globals.poidsVoisinage[etat][i][j])


	# Fonction dessinant et affichant le voisinage
	def setVoisinage(self) :
		etat = Globals.selectedState
		taille = Globals.tailleVoisinage[etat]
		diametre = (taille + 1) * 2 - 1
		self.cellSize = self.widgetSize / diametre

		# Cas d'un voisinage totalistique
		if Globals.totalistique[etat] :
			for i in range(diametre) :
				for j in range(diametre) :
					x = i * self.cellSize
					y = j * self.cellSize
					cellule = CaseVoisinage(x, y, self.cellSize, self.cellSize)
					self.addItem(cellule)
					cellule.setPen(QPen(QBrush(QColor("grey")), 1))
					if Globals.poidsVoisinage[etat][j][i] :
						cellule.setBrush(QBrush(QColor("black")))
					else :
						cellule.setBrush(QBrush(QColor("white")))
		# Cas d'un voisinage non totalistique
		else :
			for i in range(diametre) :
				for j in range(diametre) :
					x = i * self.cellSize
					y = j * self.cellSize
					cellule = CaseVoisinage(x, y, self.cellSize, self.cellSize)
					cellule.setText(Globals.poidsVoisinage[etat][j][i])
					self.addItem(cellule)


	# Fonction mettant à jour le voisinage
	def update(self) :
		self.clear()
		self.setVoisinage()


	# Fonction gérant les cliques gauche et droit
	def mousePressEvent(self, event) :
		if not Globals.typeVoisinage[Globals.selectedState] :
			self.positionClicked = event.scenePos()
			if event.button() == Qt.LeftButton :
				self.emit(SIGNAL("clicked()"))
			else :
				self.emit(SIGNAL("rightClicked()"))



""" Classe "GRILLE".
	Elle représente la grille de l'automate.

Elle reconnaît le clique droit.
Elle maintient la grille des états à jour tout en affichant la grille des couleurs
correspondantes. La grille des états étant celle nécessaire au fonctionnement interne
de l'automate et la grille des couleurs étant celle que l'utilisateur voit.
Le clique droit sur une cellule de la grille des couleurs la colore dans la couleur
de l'état actuellement sélectionné.

Dans cette classe, on trouve :
- une fonction colorant une cellule lors d'un clique droit
- une fonction générant une grille monocolore
- une fonction dessinant et affichant la grille des couleurs
- une fonction mettant à jour la grille
- une fonction gérant le clique droit"""

class Grille(QGraphicsScene) :

	# Déclaration du signal
	rightClicked = Signal()


	# Fonction d'initialisation
	def __init__(self, parent = None) :
		QGraphicsScene.__init__(self, parent)
		self.cellSize = 20
		self.rightClicked.connect(lambda: self.setColorOnRightClick())


	# Fonction permettant la coloration d'une cellule lors d'un clique droit
	def setColorOnRightClick(self) :
		x = self.positionClicked.x()
		y = self.positionClicked.y()
		i = int(x // self.cellSize)
		j = int(y // self.cellSize)
		cellule = self.itemAt(x, y)

		if cellule is not None and i >= 0 and i < Globals.grilleColonnes and j >= 0 and j < Globals.grilleLignes :
			cellule.setBrush(QBrush(Globals.colors[Globals.selectedState]))
			Globals.grille[j][i] = Globals.selectedState
			Globals.grillesAnte[Globals.etape][j][i] = Globals.selectedState
			if Globals.statutJDLM and Globals.pointsJDLM > 0 : Globals.pointsJDLM -= 1 # pénalité de points si influence sur le jeu


	# Fonction générant une grille monocolore
	def initGrille(self) :
		Globals.grille = []
		for i in range(Globals.grilleLignes) :
			Globals.grille.append([])
			for j in range(Globals.grilleColonnes) :
				Globals.grille[i].append(Globals.selectedState)
		Globals.grillesAnte = [copy.deepcopy(Globals.grille)]
		self.update()


	# Fonction dessinant et affichant la grille des couleurs
	def	drawGrille(self) :
		for i in range(Globals.grilleColonnes) :
			for j in range(Globals.grilleLignes) :
				x = i * self.cellSize
				y = j * self.cellSize
				cellule = QGraphicsRectItem(x, y, self.cellSize, self.cellSize)
				cellule.setBrush(QBrush(Globals.colors[Globals.grille[j][i]]))
				cellule.setPen(QPen(QBrush(QColor("grey")), 1))
				self.addItem(cellule)
		self.setSceneRect(0, 0, Globals.grilleColonnes*self.cellSize, Globals.grilleLignes*self.cellSize)


	# Fonction mettant à jour la grille
	def update(self) :
		self.clear()
		self.drawGrille()


	# Fonction gérant le clique droit
	def mousePressEvent(self, event) :
		if event.button() == Qt.RightButton :
			self.positionClicked = event.scenePos()
			self.emit(SIGNAL("rightClicked()"))


	# Fonction gérant la souris en mouvement lorsque bouton droit pressé
	def mouseMoveEvent(self, event) :
		if event.buttons() == Qt.RightButton :
			self.positionClicked = event.scenePos()
			self.emit(SIGNAL("rightClicked()"))



""" Classe "GRILLEVIEW".
	Elle gère la zone d'affichage de la grille.

Il s'agit d'un QGraphicsView permettant le déplacement drag & drop (clique gauche)
et le zoom (molette).

Dans cette classe, on trouve :
- une fonction gérant le zoom"""

class GrilleView(QGraphicsView) :

	# Fonction d'initialisation
	def __init__(self, scene, parent = None) :
		QGraphicsView.__init__(self, scene, parent)
		self.setAlignment(Qt.AlignCenter)
		self.setMinimumSize(QSize(500, 500))
		self.setDragMode(QGraphicsView.ScrollHandDrag) # Drag & drop
		self.zoom = 1


	# Fonction gérant le zoom
	def wheelEvent(self, event) :
		self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
		scaleFactor = 1.15

		# Zoom in
		if event.delta() > 0 :
			if self.zoom < 15 :
				self.scale(scaleFactor, scaleFactor)
				self.zoom *= scaleFactor
		# Zoom out
		else :
			if self.zoom > 0.2 :
				self.scale(1/scaleFactor, 1/scaleFactor)
				self.zoom /= scaleFactor



""" Classe "REGLESPOPUP".
	Elle gère la fenêtre pop-up permettant de personnaliser ses règles.

Un clique sur le bouton "Personnalisé" à côté de la fenêtre d'affichage des règles
ouvre le pop-up.
Dans cette nouvelle fenêtre, l'utilisateur peut entrer ses règles.
S'il appuie sur "Exporter", il les enregistre.
S'il appuie sur "Importer", il peut accéder à des règles enregistrées.
S'il appuie sur "Valider", il valide son choix de règles.
S'il appuie sur "Annuler", les règles rentrées ne sont pas prises en compte.

Dans cette classe, on trouve :
- la création de la fenêtre
- la connexion des boutons à leur fonction"""

class ReglesPopup(QDialog) :

	# Fonction d'initialisation
	def __init__(self, parent = None) :
		QDialog.__init__(self, parent)
		reglesBox = QVBoxLayout()
		self.texteRegles = QTextEdit()
		self.transformeRegles = transRegles.TransRegles()  # un objet pour transformer les règles en pseudo règles
		regles = self.transformeRegles.intToExt(Globals.regles)
		self.texteRegles.insertPlainText(str(regles))    # par défaut, affiche les règles en cours
		buttonsBox = QHBoxLayout()
		importer = QPushButton(self.trUtf8("Importer")) # Bouton d'importation
		exporter = QPushButton(self.trUtf8("Exporter")) # Bouton d'exportation
		valider = QPushButton(self.trUtf8("Valider")) # Bouton de validation
		annuler = QPushButton(self.trUtf8("Annuler")) # Bouton d'annulation

		importer.setToolTip(self.trUtf8("Importe des règles enregistrées"))
		exporter.setToolTip(self.trUtf8("Exporte les règles entrées"))
		valider.setToolTip(self.trUtf8("Valide le choix de règles"))
		annuler.setToolTip(self.trUtf8("Annule le choix de règles"))

		buttonsBox.addWidget(importer)
		buttonsBox.addWidget(exporter)
		buttonsBox.addWidget(valider)
		buttonsBox.addWidget(annuler)
		reglesBox.addWidget(self.texteRegles)
		reglesBox.addLayout(buttonsBox)

		self.setLayout(reglesBox)

		# Connexion des signaux
		valider.clicked.connect(lambda: self.accept())
		annuler.clicked.connect(lambda: self.reject())
		exporter.clicked.connect(lambda : self.exporteRegles())
		importer.clicked.connect(lambda : self.importeRegles())


	# affiche un message d'erreur dans un QMessageBox
	def afficheErreur(self, message) :
		error = QMessageBox()
		error.setInformativeText(error.trUtf8(message))
		error.setWindowTitle(error.trUtf8("Erreur"))
		spacer = QSpacerItem(300, 0, QSizePolicy.Minimum, QSizePolicy.Expanding)
		layout = error.layout()
		layout.addItem(spacer, layout.rowCount(), 0, 1, layout.columnCount())
		error.exec_()


	# Fonction d'exportation des règles
	def exporteRegles(self) :
		t = self.texteRegles.toPlainText()
		test = self.transformeRegles.extToInt(t, Globals.nombreEtats, Globals.tailleVoisinage)
		if not test :
			self.afficheErreur("Syntaxe incorrecte et/ou non respect de la configuration")
			return
		filename = QFileDialog.getSaveFileName(self, self.trUtf8("Exportation des règles"))
		if filename :
			if filename == (u'', u'') :   # si l'utilisateur décide d'annuler
				return
			try : flux = open((filename[0]+'.cmr' if (filename[0][-4:] != ".cmr") else filename[0]), "w")
			except :
				self.afficheErreur("Erreur à l'ouverture du fichier " + filename[0])
				return
			flux.write(t)
			flux.close()


	# Fonction d'importation des règles
	def importeRegles(self) :
		filename = QFileDialog.getOpenFileName(self, self.trUtf8("Importation des règles"))
		if filename :
			if filename == (u'', u'') :   # si l'utilisateur décide d'annuler
				return
			if (filename[0][-4:] != ".cmr") :
				self.afficheErreur("Fichier non conforme")
				return
			try :  flux = open(filename[0], "r")
			except :
				self.afficheErreur("Erreur à l'ouverture du fichier " + filename[0])
				return
			r = flux.read()
			flux.close()
			self.texteRegles.clear()
			self.texteRegles.insertPlainText(r)

